
#include <BFile>
#include <BStyle>
#include <BFont>
#include <BReadWrite>
#include <BCursor>
#include <BDrag>
#include <BSystem>
#include <BApplication>

#include <BPainter>
#include <BKeyEvent>
#include <BMouseEvent>
#include <BGridLayout>
#include <BHBoxLayout>
#include <BVBoxLayout>
#include <BMessageBox>
#include <BScrollBar>
#include <BDockLayout>
#include <BClass>

#include "ExpItemFormat.h"
#include "ScriptBrowser.h"
#include "SourceEdit.h"

SourceEdit::SourceEdit(BWidget *parent) : BTextEdit(parent)
{
	_selectionHovered = false;
	_selectionPressed = false;
	_draggingIndex = -1;
	_hoveredFormatItem = 0;

	this->setHorizontalScrollHint(ScrollHint_Always);
	this->setLineFeed(false);

	SourceFormat *format = new SourceFormat();
	this->setTextFormat(format);
	this->setLineSpacing(3);

	_cleanReturnAction = new BAction("clean return codes");
	_cutAction = new BAction("cut");
	_copyAction = new BAction("copy");
	_pasteAction = new BAction("paste");

	_promptBox = new PromptBox();
	_promptBox->hide();
	this->addChild(_promptBox);

	_promptFilter = new PromptListFilter();
	this->setEventFilter(_promptFilter);

	BHBoxLayout *topLayout = new BHBoxLayout(this, Part_Title);
	{
		_scriptComboBox = new BComboBox();
		_methodComboBox = new BComboBox();

		_expsButton = new BButton("exps");
		_expsButton->setCheckable(true);

		_operaButton = new BButton("opera");
		_operaButton->setCheckable(true);

		topLayout->addWidget(_scriptComboBox);
		topLayout->addWidget(_methodComboBox);
		topLayout->addWidget(_expsButton, 0);
		topLayout->addWidget(_operaButton, 0);
	}

	BHBoxLayout *bottomLayout = new BHBoxLayout(this, Part_Bottom);
	bottomLayout->setMaxHeight(15);
	{
		_lineSpinBox = new BSpinBox();
		_lineSpinBox->setAlign(Align_Center);
		_lineSpinBox->setPrefix("line:");
		_lineSpinBox->setPrefixAlign(Align_LeftCenter);
		_lineSpinBox->setReadOnly(true);

		_countSpinBox = new BSpinBox();
		_countSpinBox->setAlign(Align_Center);
		_countSpinBox->setPrefix("count:");
		_countSpinBox->setPrefixAlign(Align_LeftCenter);
		_countSpinBox->setReadOnly(true);

		this->verticalScrollBar()->setArrowVisible(true);
		this->horizontalScrollBar()->setArrowVisible(true);
		bottomLayout->addWidget(this->horizontalScrollBar());
		bottomLayout->addWidget(_lineSpinBox, 0);
		bottomLayout->addWidget(_countSpinBox, 0);
	}

	connect(this, Signal_Flushed, &SourceEdit::slotFlushed);
	connect(this, Signal_AssetChanged, &SourceEdit::slotAssetChanged);
	connect(this, Signal_TextChanged, &SourceEdit::slotTextChanged);
	connect(this, Signal_SelectionChanged, &SourceEdit::slotSelectionChanged);

	connect(_expsButton, Signal_Toggled, &SourceEdit::slotExpsButtonToggled);
	connect(_operaButton, Signal_Toggled, &SourceEdit::slotOperaButtonToggled);
	connect(_cleanReturnAction, Signal_Triggered, &SourceEdit::slotCleanReturnCodes);

	connect("fresh-menu", &SourceEdit::slotFreshMenu);
}
SourceEdit::~SourceEdit()
{
}

bool SourceEdit::query(const BString &text, BValue &value) const
{
	if (text == "break-lines")
	{
		BIntSet &lines = value.fill<BIntSet>();
		lines = _breakLines;
		return true;
	}
	if (text == "break-area")
	{
		value = _breakArea;
		return true;
	}
	return BTextEdit::query(text, value);
}

void SourceEdit::analyseSource()
{
	BSource *source = this->asset();
	_messagMap.clear();
	connect(source, Signal_Message, &SourceEdit::slotSourceMessage);
	source->setText(this->text());
	source->analyse();
	disconnect(source);

	const BString &fileName = source->name();
	SourceFormat *textFormat = (SourceFormat *)this->textFormat();
	textFormat->clear();
	const BExpHolderArray &document = source->document();
	for (int i = 0; i < document.size(); i++)
	{
		BExp *exp = document[i];
		if (!exp->info.beginWith(fileName))
			continue;
		SourceFormat::Item *item = textFormat->insert(exp);
		auto it = _messagMap.find(exp->info);
		if (it.valid())
		{
			item->error = true;
			item->errorText = *it;
		}
	}
	_messagMap.clear();
	_chunkTree.clear();
	for (int i = 0; i < document.size(); i++)
	{
		BExp *exp = document[i];
		if (exp->syntax == Syntax_Include)
			continue;
		if (exp->up)
			continue;
		if (!exp->info.beginWith(fileName))
			continue;
		createChunk(exp, _chunkTree.end());
	}
	freshExpsTree();
}

void SourceEdit::freshFormat(const BExpHolderArray &document)
{
	BSource *source = this->asset();
}
void SourceEdit::drawErrorFlag(BPainter &painter, SourceFormat::Item *item)
{
	const BImage *wavyImage = painter.realFont()->image('~');
	if (wavyImage)
	{
		BRect rect = item->rect;
		rect.top() = rect.bottom();
		rect.bottom() += wavyImage->height();
		painter.setColor(168, 0, 0);
		painter.tileImage(rect, wavyImage);
		// int count = item->rect.width() / wavyImage->width();
		// if (count < 1)
		//	count = 1;
		// BRect lineRect = this->lineRect(item->line);
		// BPoint pos(item->rect.left(), lineRect.bottom());
		// pos.x() += (item->rect.width() - wavyImage->width() * count) / 2;
		// for (int i = 0; i < count; i++)
		//{
		//	painter.drawImage(pos, wavyImage->size(), wavyImage);
		//	pos.x() += wavyImage->width();
		// }
	}
}

BString SourceEdit::hexstr(void *ptr)
{
	static const char *hexcs = "0123456789ABCDEF";
	char buffer[100];
	BULong value = (BULong)ptr;
	int pos = 99;
	buffer[--pos] = 0;
	while (value)
	{
		BByte bit = value % 16;
		buffer[--pos] = hexcs[bit];
		value = value >> 4;
	}
	buffer[--pos] = 'x';
	buffer[--pos] = '0';
	return buffer + pos;
}
const char *SourceEdit::syntaxText(Syntax syntax)
{
	switch (syntax)
	{
	case BWE::Syntax_None:
		return "None";
	case BWE::Syntax_Note:
		return "Note";
	case BWE::Syntax_Include:
		return "Include";
	case BWE::Syntax_File:
		return "File";
	case BWE::Syntax_New:
		return "New";
	case BWE::Syntax_Assign:
		return "Assign";
	case BWE::Syntax_Begin:
		return "Begin";
	case BWE::Syntax_End:
		return "End";
	case BWE::Syntax_Decl:
		return "Decl";
	case BWE::Syntax_Type:
		return "Type";
	case BWE::Syntax_Value:
		return "Value";
	case BWE::Syntax_Name:
		return "Name";
	case BWE::Syntax_Self:
		return "Self";
	case BWE::Syntax_Opera:
		return "Opera";
	case BWE::Syntax_Call:
		return "Call";
	case BWE::Syntax_Test:
		return "test";
	case Syntax_And:
		return "and";
	case Syntax_Or:
		return "or";
	case Syntax_Equal:
		return "equal";
	case BWE::Syntax_If:
		return "If";
	case BWE::Syntax_Else:
		return "Else";
	case BWE::Syntax_For:
		return "For";
	case BWE::Syntax_While:
		return "While";
	case BWE::Syntax_Switch:
		return "Switch";
	case BWE::Syntax_Case:
		return "Case";
	case BWE::Syntax_Default:
		return "Default";
	case BWE::Syntax_Continue:
		return "Continue";
	case BWE::Syntax_Break:
		return "Break";
	case BWE::Syntax_Return:
		return "Return";
	case BWE::Syntax_Function:
		return "Function";
	case BWE::Syntax_Class:
		return "Class";
	case BWE::Syntax_Access:
		return "Access";
	case BWE::Syntax_Method:
		return "Method";
	case BWE::Syntax_Inline:
		return "Inline";
	case BWE::Syntax_Const:
		return "Const";
	case BWE::Syntax_Extern:
		return "Extern";
	case BWE::Syntax_Virtual:
		return "Virtual";
	case BWE::Syntax_Max:
		return "Max";
	default:
		break;
	}
	return 0;
}

void SourceEdit::freshExpsTree()
{
	if (_expsTreeWidget)
	{
		_expsTreeWidget->clearItems();
		BSource *source = this->asset();
		const BString &fileName = source->name();
		const BExpHolderArray &document = source->document();
		for (int i = 0; i < document.size(); i++)
		{
			BExp *exp = document[i];
			if (exp->up)
				continue;
			if (!exp->info.beginWith(fileName))
				continue;
			BTreeItem *item = createTreeItem(exp);
			_expsTreeWidget->addItem(item);
			freshTreeItem(item);
		}
	}
}
BTreeItem *SourceEdit::createTreeItem(BExp *exp)
{
	BString text = syntaxText(exp->syntax);
	if (text == "Method")
		text << ':' << exp->name;
	BTreeItem *item = new BTreeItem(text);
	item->setAsset(exp);
	for (int i = 0; i < exp->subs.size(); i++)
	{
		BExp *sub = exp->subs[i];
		BTreeItem *child = createTreeItem(sub);
		child->setProperty("type", BString("sub"));
		item->addChild(child);
	}
	for (int i = 0; i < exp->params.size(); i++)
	{
		BExp *param = exp->params[i];
		BTreeItem *child = createTreeItem(param);
		child->setProperty("type", BString("param"));
		item->addChild(child);
	}
	return item;
}
void SourceEdit::freshTreeItem(BTreeItem *item)
{
	const BStyle *style = this->realStyle();
	const BString &type = item->property("type");
	item->setIcon(style->icon(type));
	for (int i = 0; i < item->childCount(); i++)
	{
		BTreeItem *child = item->child(i);
		freshTreeItem(child);
	}
}
BString SourceEdit::promptText(BTreeItem *item)
{
	BSource *source = this->asset();
	BString text;
	if (item)
	{
		BExp *exp = item->asset();
		switch (exp->syntax)
		{
		case Syntax_Type:
		{
			if (const BClass *cls = source->findClass(exp->type))
			{
				if (cls->isMeta())
					text << "type ";
				else
					text << "class ";
				text << exp->type;
			}
			else
			{
				text << exp->type;
			}
			break;
		}
		case Syntax_Decl:
			text << "type " << exp->type;
			break;
		case Syntax_Name:
			text << exp->type << ' ' << exp->name;
			break;
		case Syntax_Self:
			text << exp->type << ' ' << exp->name;
			break;
		case Syntax_Opera:
			text << exp->type << ' ' << exp->name;
			break;
		case Syntax_Call:
			if (exp->type.empty())
				text << "void";
			else
				text << exp->type;
			text << ' ' << exp->name << ';';
			break;
		case Syntax_Function:
			if (exp->type.empty())
				text << "void";
			else
				text << exp->type;
			text << ' ' << exp->name << ';';
			break;
		case Syntax_Method:
			if (exp->type.empty())
				text << "void";
			else
				text << exp->type;
			text << ' ' << exp->name << ';';
			break;
		default:
			break;
		}
	}
	return text;
}

void SourceEdit::createChunk(BExp *exp, ChunkTree::iterator it)
{
	if (it.empty())
		return;
	if (exp->syntax == Syntax_Note && exp->value.valid())
	{
		if (it.valid())
			it = it.append();
		else
			it = _chunkTree.append();
		BString beginStr = exp->info.word(exp->info.find(':') + 1);
		it->begin = beginStr.toInt();
		it->end = exp->value;
		it->exp = exp;
	}
	int index = 0;
	createChunk(exp->subs, it, index);
	for (int i = 0; i < exp->subs.size(); i++)
	{
		createChunk(exp->subs[i], it);
	}
}
void SourceEdit::createChunk(BExpHolderArray &subs, ChunkTree::iterator &it, int index)
{
	while (index < subs.size())
	{
		BExp *exp = subs[index];
		if (exp->syntax == Syntax_End)
		{
			BString str = exp->info.word(exp->info.find(':') + 1);
			it->end = str.toInt();
			break;
		}
		if (exp->syntax == Syntax_Begin)
		{
			if (it.valid())
				it = it.append();
			else
				it = _chunkTree.append();
			BString str = exp->info.word(exp->info.find(':') + 1);
			it->begin = str.toInt();
			createChunk(subs, it, ++index);
		}
		index++;
	}
}
void SourceEdit::freshChunk(ChunkTree::iterator &it)
{
	const BRect &beginRect = this->lineRect(it->begin);
	const BRect &endRect = this->lineRect(it->end);
	it->head.set(_chunkArea.left(), beginRect.top(), _chunkArea.width(), beginRect.height());
	it->head %= 60;
	it->tail.set(_chunkArea.left(), endRect.top(), _chunkArea.width(), endRect.height());
	it->tail %= 60;
	it->rect = it->head;
	if (it->visible)
	{
		it->rect.expand(it->tail);
	}
	for (int i = 0; i < it.count(); i++)
	{
		auto sub = it.seek(i);
		freshChunk(sub);
	}
}
void SourceEdit::foldedChunk(ChunkTree::iterator &it)
{
	for (int i = it->begin + 1; i <= it->end; i++)
	{
		this->setLineVisible(i, it->visible);
	}
	if (it->visible)
	{
		for (auto sub = it.begin(); sub < it.end(); sub++)
		{
			foldedChunk(sub);
		}
	}
}
SourceEdit::ChunkTree::iterator SourceEdit::chunkUnder(const BPoint &point)
{
	for (auto it = _chunkTree.begin(); it.valid(); ++it)
	{
		auto res = chunkUnder(it, point);
		if (res.valid())
			return res;
	}
	return _chunkTree.end();
}
SourceEdit::ChunkTree::iterator SourceEdit::chunkUnder(ChunkTree::iterator &it, const BPoint &point)
{
	if (it->rect.contain(point))
	{
		for (int i = 0; i < it.count(); i++)
		{
			auto sub = it.seek(i);
			auto res = chunkUnder(sub, point);
			if (res.valid())
				return res;
		}
		return it;
	}
	return it.end();
}
SourceEdit::ChunkTree::iterator SourceEdit::chunkOfLine(int line)
{
	for (auto it = _chunkTree.begin(); it.valid(); ++it)
	{
		auto res = chunkOfLine(it, line);
		if (res.valid())
			return res;
	}
	return _chunkTree.end();
}
SourceEdit::ChunkTree::iterator SourceEdit::chunkOfLine(ChunkTree::iterator &it, int line)
{
	Chunk &chunk = *it;
	if (line > it->begin && line < it->end)
	{
		for (int i = 0; i < it.count(); i++)
		{
			auto sub = it.seek(i);
			auto res = chunkOfLine(sub, line);
			if (res.valid())
				return res;
		}
		return it;
	}
	return it.end();
}
void SourceEdit::drawChunk(BPainter &painter, ChunkTree::iterator &it)
{
	if (it == _hoveredChunk)
	{
		painter.setColor(BColor(128, 156, 200));
		painter.fillRect(it->rect);
	}
	painter.setColor(BColor(28, 56, 128));
	painter.drawRect(it->head);
	if (it->visible)
	{
		if (_minusImage)
			painter.drawImage(it->head % 80, _minusImage, true);
		else
			painter.drawGraph(it->head % 80, Graph_Minus);
		painter.drawLine(it->head.bottomCenter(), it->tail.center());
		painter.drawLine(it->tail.center(), it->tail.rightCenter());
		for (int i = 0; i < it.count(); i++)
		{
			auto sub = it.seek(i);
			drawChunk(painter, sub);
		}
	}
	else
	{
		if (_plusImage)
			painter.drawImage(it->head % 80, _plusImage, true);
		else
			painter.drawGraph(it->head % 80, Graph_Plus);
	}
}
void SourceEdit::drawSpaceCode(BPainter &painter, BRect rect, BCode code)
{
	if (code == '\t' && BApplication::property("show-tabs"))
	{
		painter.drawLine(rect.leftCenter(), rect.rightCenter());
		int x = rect.right() - 5;
		int y0 = rect.center().y() - 5;
		int y1 = rect.center().y() + 5;
		painter.drawLine(BPoint(x, y0), rect.rightCenter());
		painter.drawLine(BPoint(x, y1), rect.rightCenter());
	}
	if (code == ' ' && BApplication::property("show-spaces"))
	{
		painter.setPointSmooth(true);
		painter.setPointSize(3);
		painter.drawPoint(rect.center());
		painter.setPointSize(1);
		painter.setPointSmooth(false);
	}
	if (code == '\r' && BApplication::property("show-returns"))
	{
		// rect.scale(rect.center(), 0.6);
		rect %= 60;
		painter.drawLine(rect.leftCenter(), rect.rightCenter());
		int x = rect.left() + 5;
		int y0 = rect.center().y() - 5;
		int y1 = rect.center().y() + 5;
		painter.drawLine(rect.leftCenter(), BPoint(x, y0));
		painter.drawLine(rect.leftCenter(), BPoint(x, y1));
		painter.drawLine(BPoint(rect.left(), y0), BPoint(rect.left(), y1));
	}
	if (code == '\n' && BApplication::property("show-linefeed"))
	{
		rect %= 60;
		painter.drawLine(rect.rightTop(), rect.rightBottom());
		painter.drawLine(rect.leftBottom(), rect.rightBottom());
		int x = rect.left() + 5;
		int y0 = rect.bottom() - 5;
		int y1 = rect.bottom() + 5;
		painter.drawLine(rect.leftBottom(), BPoint(x, y0));
		painter.drawLine(rect.leftBottom(), BPoint(x, y1));
	}
}

void SourceEdit::slotAssetChanged(BObject *object, const BValue &value)
{
	BSource *source = value;
	_promptBox->setSource(source);
	this->setText(source->text());
	const BString &name = source->name();
	BScript *script = source->script();
	for (int i = 0; i < script->breakpointCount(); i++)
	{
		const BString &breakpoint = script->breakpoint(i);
		if (breakpoint.beginWith(name))
		{
			int pos = breakpoint.find(':');
			int number = breakpoint.substr(pos + 1, breakpoint.size()).toInt();
			if (number >= 0)
			{
				int index = _breakLines.find(number);
				if (_breakLines.check(index))
					_breakLines.remove(index, 1);
				else
					_breakLines.insert(number);
			}
		}
	}
}
void SourceEdit::slotFreshMenu(BObject *object, const BValue &value)
{
	bool hasReturnCode = this->text().contain('\r');
	_cleanReturnAction->setEnabled(hasReturnCode);

	bool selecting = this->selectedRange().valid();
	_cutAction->setEnabled(selecting);
	_copyAction->setEnabled(selecting);

	const BString &text = BSystem::GetClippedValue();
	_pasteAction->setEnabled(text.size());

	BMenu *menu = value;
	menu->addAction(_cleanReturnAction);
	menu->addAction(_cutAction);
	menu->addAction(_copyAction);
	menu->addAction(_pasteAction);
}
void SourceEdit::slotTextChanged(BObject *object, const BValue &value)
{
	analyseSource();
}
void SourceEdit::slotSelectionChanged(BObject *object, const BValue &value)
{
	BRange range = value;
	if (_expsTreeWidget)
	{
		SourceFormat *format = (SourceFormat *)this->textFormat();
		if (const SourceFormat::Item *item = format->item(range.min()))
		{
			if (BTreeItem *expItem = _expsTreeWidget->itemOfAsset(item->exp))
			{
				expItem->setSelected(true);
				_expsTreeWidget->scrollToItem(expItem);
			}
		}
	}
}
void SourceEdit::slotFlushed(BObject *object, const BValue &value)
{
	SourceFormat *textFormat = (SourceFormat *)this->textFormat();
	const SourceFormat::ItemHolderMap &itemMap = textFormat->itemMap();
	for (auto it = itemMap.begin(); it.valid(); it++)
	{
		SourceFormat::Item *item = *it;
		int min = item->range.min();
		int max = item->range.max();
		item->rect = this->codeRect(min);
		if (item->exp && item->exp->syntax == Syntax_Note && item->exp->value.is<int>())
		{
			int line = item->line;
			for (int i = min + 1; i < max; i++)
			{
				const BRect &rect = this->codeRect(i);
				if (rect.center().y() > item->rect.bottom())
					if (!this->lineVisible(++line))
						break;
				item->rect.expand(this->codeRect(i));
			}
		}
		else
		{
			for (int i = min + 1; i < max; i++)
			{
				item->rect.expand(this->codeRect(i));
			}
		}
	}
	for (int i = 0; i < _chunkTree.count(); i++)
	{
		auto it = _chunkTree.seek(i);
		freshChunk(it);
	}
}
void SourceEdit::slotExpsButtonToggled(BObject *object, const BValue &value)
{
	if (_expsTreeWidget.empty())
	{
		_expsTreeWidget = new BTreeWidget();
		_expsTreeWidget->setTitle("Exps");
		_expsTreeWidget->setTitleVisible(true);
		_expsTreeWidget->setSelectMode(Select_Single);
		BDockLayout *dockLayout = new BDockLayout(this);
		dockLayout->addWidget(_expsTreeWidget, Align_Right, 0, 1);
		dockLayout->setSplitable(true);
		dockLayout->setSpacing(5);
		connect(_expsTreeWidget, Signal_ItemHovered, &SourceEdit::slotExpTreeItemHovered);
	}
	_expsTreeWidget->setVisible(value);
	freshExpsTree();
}
void SourceEdit::slotOperaButtonToggled(BObject *object, const BValue &value)
{
	if (_operaStackWidget.empty())
	{
		_operaStackWidget = new OperaStackWidget();
		BDockLayout *dockLayout = new BDockLayout(this);
		dockLayout->addWidget(_operaStackWidget, Align_Right, 0, 1);
		dockLayout->setSplitable(true);
		dockLayout->setSpacing(5);
		_operaStackWidget->setAsset(this);
	}
	_operaStackWidget->setVisible(value);
}
void SourceEdit::slotExpTreeItemHovered(BObject *object, const BValue &value)
{
	BTreeItem *item = value;
	BString text = promptText(item);
	_expsTreeWidget->setTips(text);
	_hoveredItem = item;
	this->fresh();
}
void SourceEdit::slotCleanReturnCodes()
{
	BString text = this->text();
	while (text.remove('\r'))
		;
	this->setText(text);
	this->emit(Signal_TextEdited, text);
}
void SourceEdit::slotSourceMessage(BObject *object, const BValue &value)
{
	const BString &text = value;
	int infoEnd = text.find(')');
	if (infoEnd < text.size())
	{
		BString info;
		text.substr(info, 0, infoEnd + 1);
		BString &message = _messagMap[info];
		text.substr(message, text.find(':', infoEnd) + 1, text.size());
	}
}

void SourceEdit::showEvent(const BEvent &event)
{
	BTextEdit::showEvent(event);
}
void SourceEdit::closeEvent(const BEvent &event)
{
	BTextEdit::closeEvent(event);
}

void SourceEdit::updateEvent(const BEvent &event)
{
	BTextEdit::updateEvent(event);
	if (_promptBox->item() && _promptBox->visible() == false)
	{
		if (event.time() - _hoverTime > 1.0)
		{
			_promptBox->setSize(_promptBox->sizeHint());
			_promptBox->setVisible(true);
		}
	}
	int line = this->query("input-line");
	int index = this->query("input-index");
	int count = this->query("input-count");
	_lineSpinBox->setValue(line);
	_countSpinBox->setValue(count);
}
void SourceEdit::resizeEvent(const BEvent &event)
{
	BTextEdit::resizeEvent(event);
	BRect rect = this->rect(Part_Left);
	rect.move(rect.width(), 0);
	int half = this->margin(Part_Left) / 2;
	_breakArea.set(rect.x(), rect.y(), half, rect.height());
	_chunkArea.set(_breakArea.right(), rect.y(), half, rect.height());
}

void SourceEdit::styleEvent(const BEvent &event)
{
	BTextEdit::styleEvent(event);
	const BFont *font = this->realFont();
	const BStyle *style = event.value();
	_minusImage = style->image("minus");
	if (_minusImage.empty() && font)
		_minusImage = font->image('-');
	_plusImage = style->image("plus");
	if (_plusImage.empty() && font)
		_plusImage = font->image('+');
	this->setMargin(Part_Left, font->size() * 2);
	if (_expsTreeWidget)
	{
		for (int i = 0; i < _expsTreeWidget->itemCount(); i++)
		{
			BTreeItem *item = _expsTreeWidget->item(i);
			freshTreeItem(item);
		}
	}
	_cursorArrowRight = new BCursor(style->image("cursor-right-arrow"), 17, 1);
}
void SourceEdit::freshEvent(const BEvent &event)
{
	const BFont *font = this->realFont();
	BString numberStr(this->lineCount());
	int left = font->textSize(numberStr).width();
	if (left < font->size())
		left = font->size();
	this->setPerch(Part_Left, left);
	BTextEdit::freshEvent(event);
}
void SourceEdit::paintEvent(const BEvent &event)
{
	BTextEdit::paintEvent(event);
	BPainter painter(this);

	painter.setColor(200, 200, 200, 200);
	painter.fillRect(_breakArea);

	BValue value;
	if (this->query("visual-range", value))
	{
		BRange lineRange = value;
		BString numberText;
		const BRect &leftRect = this->rect(Part_Left);
		int x = leftRect.x();
		int w = leftRect.width();
		painter.setColor(Color_Text);
		for (int line = lineRange.min(); line < lineRange.max(); line++)
		{
			painter.setColor(Color_Text);
			if (this->lineVisible(line))
			{
				const BRect &rect = this->lineRect(line);
				numberText.reset();
				numberText << line;
				painter.drawText(x, rect.y(), w, rect.height(), numberText, Align_Center);
			}
			painter.setColor(200, 0, 0);
			const BString &text = this->lineText(line);
			for (auto it = text.begin(); it.valid(); ++it)
			{
				if (it->isSpace())
				{
					BRect codeRect = this->codeRect(line, it.index());
					drawSpaceCode(painter, codeRect, *it);
				}
			}
		}
	}
	if (this->query("input-line", value))
	{
		int line = value;
		if (line >= 0)
		{
			const BRect &inputRect = this->lineRect(line);
			painter.setColor(128, 128, 128, 128);
			painter.setLineWidth(2);
			painter.drawRect(inputRect);
		}
		painter.setLineWidth(1);
	}

	for (int i = 0; i < _chunkTree.count(); i++)
	{
		auto it = _chunkTree.seek(i);
		drawChunk(painter, it);
	}

	BExp *focusedExp = 0;
	if (_hoveredItem)
		focusedExp = _hoveredItem->asset();
	SourceFormat *textFormat = (SourceFormat *)this->textFormat();
	const SourceFormat::ItemHolderMap &itemMap = textFormat->itemMap();
	for (auto it = itemMap.begin(); it.valid(); it++)
	{
		SourceFormat::Item *item = *it;
		if (item->exp.empty())
			continue;
		if (item->exp == focusedExp)
		{
			painter.setColor(100, 168, 100);
			painter.drawRect(item->rect);
		}
		if (item->error)
		{
			drawErrorFlag(painter, item);
		}
	}

	if (_draggingIndex >= 0)
	{
		painter.setLineWidth(2);
		painter.setColor(60, 60, 60);
		painter.drawLine(_draggingLine.begin(), _draggingLine.end());
	}
}

void SourceEdit::keyPressEvent(const BKeyEvent &keyEvent)
{
	Key key = keyEvent.key();
	BTextEdit::keyPressEvent(keyEvent);
}
void SourceEdit::keyReleaseEvent(const BKeyEvent &keyEvent)
{
	if (keyEvent.key() == Key_Enter)
	{
		int line = -1;
		this->inputPos(line);
		const BString &prev = this->lineText(line - 1);
		int tab = 0;
		for (int i = 0; i < prev.size(); i++)
		{
			if (prev[i] != '\t')
				break;
			tab++;
		}
		BString text('\t', tab);
		this->insertText(line, 0, text);
	}
	BTextEdit::keyReleaseEvent(keyEvent);
}

void SourceEdit::mousePressEvent(const BMouseEvent &mouseEvent)
{
	BPoint point = mouseEvent.pos();
	if (mouseEvent.button() == Button_Left)
	{
		if (_selectionHovered)
		{
			_selectionPressed = true;
			return;
		}
		if (_breakArea.contain(point))
		{
			int line = this->lineAt(point);
			if (line >= 0)
			{
				int index = _breakLines.find(line);
				if (_breakLines.check(index))
				{
					_breakLines.remove(index, 1);
					emit("remove-breakpoint", line);
				}
				else
				{
					_breakLines.insert(line);
					emit("create-breakpoint", line);
				}
				this->fresh();
			}
		}
		if (_hoveredChunk.valid())
		{
			_hoveredChunk->visible = !_hoveredChunk->visible;
			foldedChunk(_hoveredChunk);
		}
	}
	BTextEdit::mousePressEvent(mouseEvent);
}
void SourceEdit::mouseReleaseEvent(const BMouseEvent &mouseEvent)
{
	if (mouseEvent.button() == Button_Left)
	{
		if (_selectionPressed)
		{
			_selectionPressed = false;
			this->fresh();
			return;
		}
	}
	BTextEdit::mouseReleaseEvent(mouseEvent);
}
void SourceEdit::mouseMoveEvent(const BMouseEvent &mouseEvent)
{
	if (_selectionPressed)
		return;
	BPoint pos = mouseEvent.pos();
	if (mouseEvent.button() == Button_None)
	{
		_selectionHovered = false;
		BRange selectRange = this->query("select-range");
		if (selectRange.valid())
		{
			for (int i = selectRange.min(); i < selectRange.max(); i++)
			{
				BRect rect = this->codeRect(i);
				_selectionHovered = rect.contain(pos);
				if (_selectionHovered)
					break;
			}
		}
		if (_selectionHovered)
		{
			mouseEvent.accept(Cursor_Arrow);
		}
		else if (_breakArea.contain(pos))
		{
			mouseEvent.accept(Cursor_Hand);
		}
		else if (this->rect(Part_Left).contain(pos))
		{
			mouseEvent.accept(_cursorArrowRight);
		}
		else if (this->centerRect().contain(pos))
		{
			_hoveredFormatItem = 0;
			SourceFormat *textFormat = (SourceFormat *)this->textFormat();
			const SourceFormat::ItemHolderMap &itemMap = textFormat->itemMap();
			for (auto it = itemMap.begin(); it.valid(); it++)
			{
				SourceFormat::Item *item = *it;
				if (!item->exp)
					continue;
				if (!this->lineVisible(item->line))
					continue;
				if (!item->rect.contain(pos))
					continue;
				_hoveredFormatItem = item;
				break;
			}
			if (_promptBox->item() != _hoveredFormatItem)
			{
				if (_hoveredFormatItem)
				{
					pos.y() = _hoveredFormatItem->rect.bottom();
					_promptBox->setPos(pos);
				}
				_promptBox->hide();
				_promptBox->setItem(_hoveredFormatItem);
				_hoverTime = mouseEvent.time();
			}
			ChunkTree::iterator hoveredChunk = _chunkTree.end();
			if (_chunkArea.contain(pos))
			{
				hoveredChunk = chunkUnder(pos);
			}
			if (_hoveredChunk != hoveredChunk)
			{
				_hoveredChunk = hoveredChunk;
				this->fresh();
			}
		}
	}
	BTextEdit::mouseMoveEvent(mouseEvent);
}
void SourceEdit::mouseDragEvent(const BMouseEvent &mouseEvent)
{
	if (_selectionPressed)
	{
		BDrag *drag = new BDrag(this);
		drag->setImage(this->realStyle()->image("dragging"));
		if (drag->execute())
		{
		}
		return;
	}
	BTextEdit::mouseDragEvent(mouseEvent);
}
void SourceEdit::mouseClickEvent(const BMouseEvent &mouseEvent)
{
	if (mouseEvent.keyState(Key_Ctrl))
	{
		const BPoint &pos = mouseEvent.pos();
		if (_hoveredFormatItem)
		{
			emit("goto-declaration", _hoveredFormatItem->exp);
		}
	}
	BTextEdit::mouseClickEvent(mouseEvent);
}
void SourceEdit::mouseDoubleClickEvent(const BMouseEvent &mouseEvent)
{
	BTextEdit::mouseDoubleClickEvent(mouseEvent);
	if (_hoveredChunk.valid())
	{
		_hoveredChunk->visible = !_hoveredChunk->visible;
		foldedChunk(_hoveredChunk);
	}
}

void SourceEdit::dragEnterEvent(const BMouseEvent &event)
{
}
void SourceEdit::dragLeaveEvent(const BMouseEvent &event)
{
}
void SourceEdit::dragMoveEvent(const BMouseEvent &event)
{
	const BPoint &pos = event.pos();
	int index = this->indexAt(pos);
	BRange range = this->selectedRange();
	if (range.contain(index))
	{
		this->setCursor(Cursor_Forbidden);
		index = -1;
	}
	else
	{
		this->setCursor(Cursor_Arrow);
	}
	if (_draggingIndex != index)
	{
		_draggingIndex = index;
		int line = this->lineAt(pos);
		const BRange &lineRange = this->lineRange(line);
		index -= lineRange.min();
		if (this->code(line, index - 1) == '\n')
		{
			_draggingIndex--;
			index--;
		}
		if (lineRange.empty())
		{
			BRect rect = this->lineRect(line);
			_draggingLine.setBegin(rect.leftTop());
			_draggingLine.setEnd(rect.leftBottom());
		}
		else if (index < lineRange.size())
		{
			BRect rect = this->codeRect(line, index);
			_draggingLine.setBegin(rect.leftTop());
			_draggingLine.setEnd(rect.leftBottom());
		}
		else
		{
			BRect rect = this->codeRect(line, index - 1);
			_draggingLine.setBegin(rect.rightTop());
			_draggingLine.setEnd(rect.rightBottom());
		}
		this->fresh();
	}
}
void SourceEdit::dragDropEvent(const BMouseEvent &event)
{
	BString text = this->selectedText();
	if (_draggingIndex >= 0)
	{
		BRange range = this->selectedRange();
		if (_draggingIndex < range.min())
		{
			this->removeSelection();
			this->insertText(_draggingIndex, text);
		}
		if (_draggingIndex > range.max())
		{
			this->insertText(_draggingIndex, text);
			this->removeSelection();
		}
	}
	_selectionPressed = false;
	_draggingIndex = -1;
	this->fresh();
}
